﻿using System;
using SoftwareConsulting.BI11.NumericalMethods.Statistics;
using SoftwareConsulting.BI11.NumericalMethods.Curves;
using SoftwareConsulting.BI11.Studio.InternalServices;

namespace SoftwareConsulting.BI11.NumericalMethods.Statistics
{
    /// <summary>
    /// Класс распределения Лапласа
    /// </summary>
    public sealed class LaplaceDistribution : ProbabilityDensityFunction
    {	    
        private double _muDistributionMiddle;//центр распределения	    
        private double _betaDistributionScale;//масштаб распределения

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="muDistributionMiddle">центр распределения</param>
        /// <param name="betaDistributionScale">масштаб распределения</param>
        public LaplaceDistribution(double muDistributionMiddle, double betaDistributionScale)
        {
            if (betaDistributionScale <= 0)
                throw new ArgumentOutOfRangeException("scale");
            _muDistributionMiddle = muDistributionMiddle;
            _betaDistributionScale = betaDistributionScale;
        }

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="histogram">Гистограмма, из которой вытягиваются параметры по наилучшим предположениям</param>
        public LaplaceDistribution(Histogram h)
                        : this( h.Average, Math.Sqrt( 0.5 * h.Variance))
        {
        }

        /// <summary>
        /// Возвращает среднее распределения
        /// </summary>
        public override double Average
        {
            get 
            { 
                return _muDistributionMiddle; 
            }
            set
            {
                Assert.Fail(Assert.Id.Assert00287);//для этого распределения не поддерживается
            }
        }

        /// <summary>
        /// Задает новые параметры
        /// </summary>        
        /// <param name="muDistributionMiddle">центр распределения</param>
        /// <param name="betaDistributionScale">масштаб распределения</param>
        public void SetParameters(double muDistributionMiddle, double betaDistributionScale)
        {
            _muDistributionMiddle = muDistributionMiddle;
            _betaDistributionScale = betaDistributionScale;
        }

        /// <summary>
        /// Возвращает значение функции плотности распределения
        /// </summary>
        /// <param name="x"></param>
        /// <returns>вероятность, нахождения случайной переменной меньше или равной, чем X</returns>
        public override double GetDistributionValue(double x)
        {
            return x > _muDistributionMiddle
                    ? 1 - Math.Exp(-(x - _muDistributionMiddle) / _betaDistributionScale) / 2
                    : Math.Exp(-(x - _muDistributionMiddle) / _betaDistributionScale) / 2;
        }

        /// <summary>
        /// Возвращает эксцесс распределения
        /// </summary>
        public override double Kurtosis
        {
            get 
            { 
                return 3; 
            }
        }

        /// <summary>
        /// Возвращает название распределения
        /// </summary>
        public override string Name
        {
            get 
            { 
                return "Laplace distribution"; 
            }
        }

        /// <summary>
        /// Возвращает/устанавливает параметры распределения
        /// </summary>
        public override double[] Parameters
        {
            get 
            { 
                return new double[] { _muDistributionMiddle, _betaDistributionScale }; 
            }
            set 
            { 
                SetParameters(value[0], value[1]); 
            }
        }

        /// <summary>
        /// Возвращает значение, для которого функция распределения равна указанному значению X
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        protected override double GetInversedValue(double x)
        {
            return x < 0.5
                    ? _muDistributionMiddle + _betaDistributionScale * Math.Log(2 * x)
                    : _muDistributionMiddle - _betaDistributionScale * Math.Log(2 - 2 * x);
        }

        /// <summary>
        /// Возвращает случайное вещественное число в соответствие с распределением
        /// </summary>
        /// <returns></returns>
        public override double GetRandomValue()
        {
            double r = -_betaDistributionScale * Math.Log(this.RandomGenerator.NextDouble());
            return this.RandomGenerator.NextDouble() > 0.5 ? _muDistributionMiddle + r : _muDistributionMiddle - r;
        }

        /// <summary>
        /// Возвращает коэффициент уклона распределения
        /// </summary>
        public override double Skewness
        {
            get 
            { 
                return 0; 
            }
        }

        /// <summary>
        /// Возвращает стандартное отклонение распределения
        /// </summary>
        public override double StandardDeviation
        {
            get 
            { 
                return _betaDistributionScale / Math.Sqrt(2); 
            }
        }

        /// <summary>
        /// Возвращает как строку
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return string.Format("{0} ({1:####0.00000},{2:####0.00000})", Name, _muDistributionMiddle, _betaDistributionScale);
        }

        /// <summary>
        /// Вычисляет значение, основываясь на функции плотности
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        public override double GetValue(double x)
        {
            return Math.Exp(-Math.Abs(x - _muDistributionMiddle) / _betaDistributionScale) / (2 * _betaDistributionScale);
        }

        /// <summary>
        /// Возвращает значение распределения и градиент распределения с учетом параметров
        /// </summary>
        /// <param name="x"></param>
        /// <returns>0й элемент - значение распределения, 1й и последующие - градиент распределения</returns>
        public override double[] GetValueAndGradient(double x)
        {
            double[] result = new double[3];
            result[0] = this.GetValue(x);
            double y = x - _muDistributionMiddle;
            if (y >= 0)
            {
                result[1] = result[0] / _betaDistributionScale;
                result[2] = (y / _betaDistributionScale - 1) * result[0] / _betaDistributionScale;
            }
            else
            {
                result[1] = -result[0] / _betaDistributionScale;
                result[2] = -(y / _betaDistributionScale + 1) * result[0] / _betaDistributionScale;
            }
            return result;
        }
    }
}
